--[[ Addon handling

Like WoW, we open and read TOC files first.
We then resolve dependency between addons.

]]

local open = io.open
local select = select
local insert = table.insert
local match = string.match
local gsub = string.gsub
local find = string.find
local split = string.split

local FRAMEXML_REQUIRED_VERSION = 20000
local FRAMEXML_CURRENT_VERSION = config.TOC

local SAVE_ACCOUNT_FMT = "save/account/SavedVariables/%s.lua"
local SAVE_CHAR_FMT = "save/character/SavedVariables/%s.lua"

Addons = { addons = {}, disabled = {}, loaded = {}, deps = {} }

local Addons = Addons
local addons = Addons.addons

local saves = {}

local function RegisterForSave(addon, perchar, name)
	local s = saves[addon]
	if not s then
		s = {}
		saves[addon] = s
	end
	s[name] = perchar and true or false
end

function Addons.SaveRegisteredData()
	for addon, data in pairs(saves) do
		local gf = io.open(string.format(SAVE_ACCOUNT_FMT, addon), "w")
		local pf = io.open(string.format(SAVE_CHAR_FMT, addon), "w")
		for name, perchar in pairs(data) do
			utils.dump(name, api.current[name], perchar and pf or gf)
		end
		gf:close()
		pf:close()
	end
end

function Addons.ReadTocInfo(name, path, security)
	local tocname = path.."/"..name.."/"..name..".toc"
	if name == "Blizzard_BarberShopUI" then
		-- Blizzard has a incorrect path here for case-sensitive filesystems
		tocname = path.."/Blizzard_BarbershopUI/"..name..".toc"
	end
	if not security then
		security = "INSECURE"
	end
	local toc = {
		name = name,
		security = security,
		data = {},
		files = { "Bindings.xml", },
		loadwith = {},
		path = tocname:gsub("/[^/]*$", "")
	}
	log("** Loading table of contents %s", tocname)
	local f, err = open(tocname, "r")
	if not f then
		log("EE unable to open toc file : %s", err)
		return
	end
	for line in f:lines() do
		line = gsub(line, "[\r\n]", "")
		local name, value = match(line, '## ?([^:]+): ?(.*)$')
		if name then
			name = name:trim()
			toc.data[name] = value
		elseif line:sub(1, 1) ~= '#' then
			line = line:trim()
			if line:len() > 0 then
				insert(toc.files, line)
			end
		end
	end
	insert(addons, toc)

	if Addons.disabled[name] == nil then
		local disabled
		if toc.data.DefaultState == "disabled" then
			disabled = true
		else
			disabled = false
		end
		Addons.disabled[name] = disabled
	end
	return toc
end

local function strip(s)
	local s = gsub(gsub(s, "^%s+", ""), "%s+$", "")
	return s
end

local function append_deps(list, value, ...)
	for i = 1, select("#", ...) do
		local dep = select(i, ...)
		if dep ~= "" and list[dep] ~= "R" then
			list[dep] = value
		end
	end
end

local function checkdeps(addon)
	if addon.deps then return end
	addon.deps = {}

	local deps = addon.data["Dependencies"]
	if deps then
		append_deps(addon.deps, "R", split(" ,", deps))
	end
	deps = addon.data["OptionalDeps"]
	if deps then
		append_deps(addon.deps, "O", split(" ,", deps))
	end
	deps = addon.data["LoadWith"]
	if deps then
		append_deps(addon.deps, "R", split(" ,", deps))
	end
end

local dep_reasons = {
	BANNED = true,
	CORRUPT = true,
	DISABLED = true,
	INCOMPATIBLE = true,
	INSECURE = true,
	INTERFACE_VERSION = true,
	MISSING = true,
	NOT_DEMAND_LOADED = true,
}

local function checkloadable(addon)
	if addon.loadable ~= nil then return end
	if Addons.disabled[addon.name] then
		addon.loadable = false
		addon.reason = "DISABLED"
		return
	end
	if not addon.data["Interface"] or tonumber(addon.data["Interface"]) < FRAMEXML_REQUIRED_VERSION then
		log("EE Invalid version for %s : %s", addon.name, addon.data["Version"])
		addon.loadable = false
		addon.reason = "INTERFACE_VERSION"
		return
	end
	if tonumber(addon.data["Interface"]) ~= FRAMEXML_CURRENT_VERSION then
		log("WW Unexpected version for %s : %s", addon.name, addon.data["Interface"])
	end
	for name, value in pairs(addon.deps) do
		if value == "R" then
			local dep = Addons.GetAddOnInfo(name)
			if not dep then
				addon.loadable = false
				addon.reason = "DEP_MISSING"
				return
			end
			checkloadable(dep)
			if not dep.loadable then
				addon.loadable = false
				if dep_reasons[dep.reason] then
					addon.reason = "DEP_"..dep.reason
				elseif dep.reason:sub(1, 4) == "DEP_" and dep_reasons[dep.reason:sub(5, -1)] then
					addon.reason = dep.reason
				else
					addon.reason = "UNKNOWN_ERROR"
				end
				return
			end
		end
	end
	addon.loadable = true
end

local function cleanoptdeps(addon)
	for name, value in pairs(addon.deps) do
		if value == "O" then
			local dep = Addons.GetAddOnInfo(name)
			if not dep or not dep.loadable then
				addon.deps[name] = nil
			end
		end
	end
end

local function markloadwith(name, ...)
	for i = 1, select("#", ...) do
		local n = select(i, ...)
		if n ~= "" then
			local addon = Addons.GetAddOnInfo(n)
			if addon then
				addon.loadwith[name] = true
			end
		end
	end
end

local function markloadmanagers(addon, ...)
	for i = 1, select("#", ...) do
		local n = select(i, ...)
		if n ~= "" then
			local manager = Addons.GetAddOnInfo(n)
			if manager then
				addon.loadondemand = true
				return
			end
		end
	end
end

function Addons.ResolveDependencies()
	for _, addon in ipairs(addons) do
		checkdeps(addon)
	end
	for _, addon in ipairs(addons) do
		checkloadable(addon)
	end
	for _, addon in ipairs(addons) do
		cleanoptdeps(addon)
	end
	for _, addon in ipairs(addons) do
		if addon.data["LoadOnDemand"] == "1" then
			addon.loadondemand = true
		else
			local lm = addon.data["LoadManagers"]
			if lm then
				markloadmanagers(addon, split(" ,", lm))
			end
		end
		if addon.loadondemand then
			local lw = addon.data["LoadWith"]
			if lw then
				markloadwith(addon.name, split(" ,", lw))
			end
		end
	end
end


function Addons.GetAddOnInfo(id)
	local t = type(id)
	if t == "string" then
		for i, addon in ipairs(addons) do
			if addon.name == id then
				return addon
			end
		end
	elseif t == "number" then
		if id > 0 and id <= #addons then
			return addons[id]
		end
	end
end

function Addons.AfterBlizzardAddons()
	Addons.hidden_count = #addons
end

local function LoadAddonVariables(addon)
	local vars = addon.data["SavedVariables"]
	if vars then
		api.loadlua(string.format(SAVE_ACCOUNT_FMT, addon.name), addon.name, addon.security, true)
		local save = function (...)
			for i = 1, select("#", ...) do
				local n = select(i, ...)
				if n ~= "" then
					RegisterForSave(addon.name, false, n)
				end
			end
		end
		save(split(" ,", vars))
	end
	vars = addon.data["SavedVariablesPerCharacter"]
	if vars then
		api.loadlua(string.format(SAVE_CHAR_FMT, addon.name), addon.name, addon.security, true)
		local save = function (...)
			for i = 1, select("#", ...) do
				local n = select(i, ...)
				if n ~= "" then
					RegisterForSave(addon.name, true, n)
				end
			end
		end
		save(split(" ,", vars))
	end
end

function Addons.Load(ondemand, name)
	local addon -- = addons[name]
	for _, a in ipairs(addons) do
		if a.name == name then
			addon = a
			break
		end
	end
	if not addon then
		return nil, "MISSING"
	end
	if Addons.loaded[name] then
		return true
	end
	if ondemand and not addon.loadondemand then
		return nil, "NOT_DEMAND_LOADED"
	end
	if not addon.loadable then
		return nil, addon.reason
	end
	for dep in pairs(addon.deps) do
		local l, r = Addons.Load(false, dep)
		if not l then
			return nil, r
		end
	end

	log("** Loading add-on %s", addon.name)
	Addons.loaded[name] = true

	local path = addon.path.."/"
	for _, file in ipairs(addon.files) do
		local ext = string.sub(file, -4, -1)
		if ext == ".xml" then
			api.loadxml(path..file:gsub("\\", "/"), name, addon.security)
		elseif ext == ".lua" then
			api.loadlua(path..file:gsub("\\", "/"), name, addon.security)
		end
	end
	if name == "FrameXML" then
		api.loadlua(string.format(SAVE_ACCOUNT_FMT, name), name, addon.security, true)
		api.loadlua(string.format(SAVE_CHAR_FMT, name), name, addon.security, true)
	else
		LoadAddonVariables(addon)
	end

	UI.BroadcastEvent("ADDON_LOADED", name)
	for a in pairs(addon.loadwith) do
		Addons.Load(true, a)
	end
	return true
end

function Addons.LoadAllAddons()
	local names = {}
	for _, addon in ipairs(addons) do
		if (not addon.loadondemand) and addon.loadable then
			names[#names + 1] = addon.name
		end
	end
	if config.LOAD_ALL_BLIZZARD_ADDONS then
		for _, name in ipairs(config.BLIZZARD_ADDONS) do
			names[#names + 1] = "Blizzard_"..name
		end
	end
	table.sort(names)
	Addons.Load(nil, "FrameXML")
	for _, name in ipairs(names) do
		Addons.Load(nil, name)
	end
	UI.BroadcastEvent("VARIABLES_LOADED")
end

function Addons.LoadDisabledList()
	pcall(dofile, "save/disabled.lua")
end

function Addons.SaveDisabledList()
	local f = io.open("save/disabled.lua", "w")
	for addon, value in pairs(Addons.disabled) do
		f:write(string.format("Addons.disabled[%q] = %s\n", addon, value and "true" or "false"))
	end
	f:close()
end

Addons.RegisterForSave = RegisterForSave

if TEST then
	local utils = require("utils")

	local function test()
		local toc = ReadTocInfo("FrameXML", "../base", "SECURE")
		utils.dump("toc", toc)
	end

	test()

end

